/******************************************************************************
*
* This file is part of FreeTrack (http://code.google.com/p/qtfreetrack).
* BY Willy Gardiol (willy@gardiol.org)
*
* FreeTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FreeTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/

#include "imagepng.h"

ImagePng::ImagePng(const QString& fn ) :
   Image(fn)
{
    row_pointer = NULL;
    image_width = image_height = 0;
    pFile = NULL;
    scale_factor = 1;
    bit_depth = color_type = interlace_type = channels = full_height = full_width = current_row = 0;
    row_bytes = 0;
    png_ptr = NULL;
    info_ptr = NULL;
}

ImagePng::~ImagePng()
{
    if ( reading )
    {
        png_destroy_read_struct(&png_ptr, &info_ptr,NULL);
        delete [] row_pointer;
        fclose(pFile);
    }
}

bool ImagePng::startReading( int zoom, int& output_width, int& output_height )
{
    uchar header[8];

    if ( writing || reading )
        return false;

    // Accept ONLY valid zooms
    switch (zoom)
    {
    case 256:
    case 128:
    case 64:
    case 32:
    case 16:
    case 8:
    case 4:
    case 2:
    case 1:
        scale_factor = zoom;
        break;
    default:
        return false;
    }

    // Open the image file
    if ( (pFile = fopen( filename.toAscii(), "rb" )) == NULL )
        return false;

    // Load file hader
    fread(header, 1, 8, pFile);
    // Is this a real PNG file?
    if ( png_sig_cmp(header, 0, 8) != 0 )
    {
       fclose( pFile );
       return false;
    }

    // Create the PNG reading structure
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
    // exit on error...
    if (png_ptr == NULL )
        return false;

    // Create the PNG info structure
    info_ptr = png_create_info_struct(png_ptr);
    // exit on error...
    if (info_ptr == NULL )
    {
       png_destroy_read_struct(&png_ptr,NULL,NULL);
       return false;
    }

    // This is to read data after the image? we do not need it here...
/*      png_infop end_info = png_create_info_struct(png_ptr);
    if (!end_info)
    {
         png_destroy_read_struct(&png_ptr, &info_ptr,
           (png_infopp)NULL);
         return (ERROR);
    }*/

    // Set file pointer into libpng
    png_init_io(png_ptr, pFile);
    // Tell the library we have already read the initial signature
    png_set_sig_bytes(png_ptr, 8);
    // Change zlib compression buffer
    //png_set_compression_buffer_size(png_ptr, buffer_size); normal is 8192
    // Configure the library to ignore unknown chunks
    png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_NEVER, '\0', 0);
    // Read the image info data...
    png_read_info(png_ptr, info_ptr);

  /*
      bit_depth      - holds the bit depth of one of the
                       image channels.  (valid values are
                       1, 2, 4, 8, 16 and depend also on
                       the color_type.  See also
                       significant bits (sBIT) below).
      color_type     - describes which color/alpha channels
                           are present.
                       PNG_COLOR_TYPE_GRAY
                          (bit depths 1, 2, 4, 8, 16)
                       PNG_COLOR_TYPE_GRAY_ALPHA
                          (bit depths 8, 16)
                       PNG_COLOR_TYPE_PALETTE
                          (bit depths 1, 2, 4, 8)
                       PNG_COLOR_TYPE_RGB
                          (bit_depths 8, 16)
                       PNG_COLOR_TYPE_RGB_ALPHA
                          (bit_depths 8, 16)

                       PNG_COLOR_MASK_PALETTE
                       PNG_COLOR_MASK_COLOR
                       PNG_COLOR_MASK_ALPHA

      filter_method  - (must be PNG_FILTER_TYPE_BASE
                       for PNG 1.0, and can also be
                       PNG_INTRAPIXEL_DIFFERENCING if
                       the PNG datastream is embedded in
                       a MNG-1.0 datastream)
      compression_type - (must be PNG_COMPRESSION_TYPE_BASE
                       for PNG 1.0)
      interlace_type - (PNG_INTERLACE_NONE or
                       PNG_INTERLACE_ADAM7)
   */

    // Read PNG image info data
    full_width     = png_get_image_width(png_ptr,info_ptr);
    full_height    = png_get_image_height(png_ptr,info_ptr);
    bit_depth      = png_get_bit_depth(png_ptr,info_ptr);
    color_type     = png_get_color_type(png_ptr,info_ptr);
    interlace_type = png_get_interlace_type(png_ptr,info_ptr);
    channels = png_get_channels(png_ptr, info_ptr);
    row_bytes = png_get_rowbytes(png_ptr, info_ptr);

    // Now lets manipulate things a bit to our likings...

    // Get rid of palettes, we do not want them
    if (color_type == PNG_COLOR_TYPE_PALETTE)
        png_set_palette_to_rgb(png_ptr);
    // Make sure each channel uses a full byte on grayscale images!
    if ( (color_type == PNG_COLOR_TYPE_GRAY) && (bit_depth < 8) )
        png_set_expand_gray_1_2_4_to_8(png_ptr);
    // do this its useful for some images...
    if ( png_get_valid(png_ptr, info_ptr,PNG_INFO_tRNS))
        png_set_tRNS_to_alpha(png_ptr);
    // Make sure we do not have a 16bit image
    if (bit_depth == 16)
        png_set_strip_16(png_ptr);
    // Get rid of alpha channel (we do not use it at the moment
    if (color_type & PNG_COLOR_MASK_ALPHA)
        png_set_strip_alpha(png_ptr);
    // We want at least 8bits per channel!
    if (bit_depth < 8)
          png_set_packing(png_ptr);
    // Convert any grayscale to RGB
    if ( (color_type == PNG_COLOR_TYPE_GRAY) || (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) )
        png_set_gray_to_rgb(png_ptr);
    // Now, we DO NOT PROCESS interlaced images, so quit now before its too late!
    if ( png_set_interlace_handling(png_ptr) > 1 )
    {
        png_destroy_read_struct(&png_ptr,&info_ptr,NULL);
        return false;
    }

    // allocate row
    row_pointer = new uchar[ row_bytes ];
    // initialize the row counter
    current_row = 0;

    // Calculate scaled with/height
    image_width = output_width = full_width / scale_factor;
    image_height = output_height = full_height / scale_factor;
    reading = true;
    return true;
}

uchar* ImagePng::readScanline()
{
    if ( !reading )
        return NULL;

    // Iterate on scale_factor lines (we should bland the data here: TODO)
    for (int s = 0; s < scale_factor; s++ )
    {
        if ( current_row < full_height )
            png_read_row( png_ptr, row_pointer, NULL );
        else
            return NULL;
    }
    // Remember, we also need to "compress" the scanline. Again, here we could use
    // some kind of nice smoothing... TODO!
    if ( scale_factor > 1 )
    {
        for (int x = 0; x < image_width; x++ )
        {
            int dst = x*COLOR_CHANNELS;
            int src = x*COLOR_CHANNELS*scale_factor;
            row_pointer[ dst ] = row_pointer[ src ];
            row_pointer[ dst+1 ] = row_pointer[ src+1 ];
            row_pointer[ dst+2 ] = row_pointer[ src+2 ];
        }
    }
    return row_pointer;
}

bool ImagePng::readImage(QImage *dest_buffer, const QRect& source_rect, const QRect& dest_rect, int zoom )
{
    // Reset image to empty...
    dest_buffer->fill(0);

    int source_width,
        source_height;
    int dest_width = dest_buffer->width(),
        dest_height = dest_buffer->height();
    int extra_cols = 0;

    if ( !startReading(zoom, source_width, source_height ) )
        return false;

    // quit if size differs
    if ( source_width > dest_width )
        source_width = dest_width;
    if ( source_height > dest_height )
        source_height = dest_height;
    if ( source_width < dest_width )
        extra_cols = dest_width - source_width;

    uchar* tmp;
    // Get pointer to dest buffer and place it to the beginning of the
    // first pixel do te be copied
    uchar* dest_ptr = dest_buffer->bits();
    // Prescale width
    int byte_width = source_width*3;
    // Prescale extra_cols
    extra_cols *= 3;
    // Read all the interesting rows...
    for ( int row = 0; row < source_height; row++ )
    {
        if ( (tmp = readScanline()) != NULL )
        {
            memcpy( dest_ptr, tmp, byte_width );
            dest_ptr = &dest_ptr[byte_width + extra_cols];
       }
        else
            return false;
    }
    return true;
}

bool ImagePng::isValid()
{
    FILE* pf;
    uchar header[8];
    png_structp l_png_ptr;
    png_infop l_info_ptr;

    // Open the image file
    if ( (pf = fopen( filename.toAscii(), "rb" )) == NULL )
        return false;
    // Load file hader
    fread(header, 1, 8, pf);
    // Is this a real PNG file?
    if ( png_sig_cmp(header, 0, 8) != 0 )
    {
       fclose( pf );
       return false;
    }
    // Create the PNG reading structure
    l_png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
    // exit on error...
    if (l_png_ptr == NULL )
    {
        fclose(pf);
        return false;
    }
    // Create the PNG info structure
    l_info_ptr = png_create_info_struct(l_png_ptr);
    // exit on error...
    if (l_info_ptr == NULL )
    {
       png_destroy_read_struct(&l_png_ptr,NULL,NULL);
       fclose(pf);
       return false;
    }
    png_init_io(l_png_ptr, pf);
    png_set_sig_bytes(l_png_ptr, 8);
    png_set_keep_unknown_chunks(l_png_ptr, PNG_HANDLE_CHUNK_NEVER, '\0', 0);
    png_read_info(l_png_ptr, l_info_ptr);

    image_width = png_get_image_width(l_png_ptr,l_info_ptr);
    image_height = png_get_image_height(l_png_ptr,l_info_ptr);

    png_destroy_read_struct(&l_png_ptr,&l_info_ptr,NULL);
    fclose(pf);
    return ( image_width > 0 ) && ( image_height > 0 );
}

bool ImagePng::writeImage( const QImage& )
{
    return false;
}
